/*******************************************************************
 * Copyright (C) yel_hb-USTC, 2012                                
 *                                                                  
 * FILE NAME             :  MLP.cpp                                 
 * PRINCIPAL AUTHOR      :  yel_hb                               
 * MODULE NAME           :  MLP                             
 * LANGUAGE              :  C++                                      
 * TARGET ENVIRONMENT    :  windows                                   
 * DATE OF FIRST RELEASE :  2013/1/4                              
 * DESCRIPTION           :  implementation the functions defined in
 *							the MLP.H
 ******************************************************************/

/*
 * Revision log:
 *
 * Created by yel_hb,2013/1/4
 *
 */

#include <cmath>
#include <ctime>
#include <iostream>
#include "MLP.H"
#include "utils.h"
#include "dmvector.h"
#include "kahansum.h"
using namespace std;

MLP::MLP ()
{
	wn = 0;
	z = 0;
	zp = 0;
	eta = 0;
	en = 0;
}

MLP::MLP ( int n, int k, double eta )
{
	this->n = n;
	this->k = k;
	V.resize ( k, DMVector ( n ));
	vn = DMVector ( k );
	W = DMVector ( k );
	wn = 0;
	Y = DMVector ( k );
	Yp = DMVector ( k );
	z = 0;
	zp = 0;
	en = 0;
	E = DMVector ( k );
	this->eta = eta;
}

bool MLP::classify ( const DMVector& X )
{
	DMVector dx = X;

	forward ( X );
	if ( z < 0 )
		return false;
	else
		return true;
}

void MLP::reset()
{
	int i, j;
	double norm;
	DMVector dv, C;

	for ( i = 0; i < k; ++i )
	{
		for ( j = 0; j < n; ++j )
		{
			dv.set ( j, gaussianNormal() + j * 1.0 / 1000);
		}

		norm = dv.norm();
		for ( j = 0; j < n; ++j )
		{
			dv.set ( j, dv.get ( j ) / norm );
		}
		
		set_hyperplane_weights ( i, C, dv );
	}
	
	
	dv = DMVector ( k );
	for ( j = 0; j < k; ++j )
	{
		dv.set ( j, gaussianNormal());
	}

	norm = dv.norm();
	for ( j = 0; j < k; ++j )
	{
		dv.set ( j, dv.get ( j ) / norm );
	}
	W = dv;
	srand( (unsigned)time( NULL ) );
	wn = 0;
}

void MLP::update ( const DMVector& X, bool inClass )
{
	forward ( X );
	backward ( X, inClass == true ? 1 : -1 );
}

void MLP::set_hyperplane_weights ( size_t i, DMVector& C, const DMVector& N )
{
	srand( (unsigned)time( NULL ) );
	DMVector tmpN = N;
	V[i] = tmpN;
	vn.set ( i, ( rand() % 10 - 5 ) / 10);
}

void MLP::forward ( const DMVector& X )
{
	int i, j;
	KahanSum sum;
	double f, fp;

	for ( j = 0; j < k; ++j )
	{
		sum.reset();

		for ( i = 0; i < n; ++i )
		{
			 sum.add ( V[j].get ( i ) * X.get ( i ) );
		}
		sum.add ( vn.get ( j ) );
		f = sum.get_sum();
		fp = 1 - tanh ( f ) * tanh ( f );
		Y.set ( j, tanh ( f ) );
		Yp.set ( j, fp );
	}

	sum.reset();
	for ( i = 0; i < k; ++i )
	{
		sum.add ( Y.get ( i ) * W.get ( i ));
	}
	sum.add ( wn );
	f = sum.get_sum();
	fp = 1 - tanh ( f ) * tanh ( f );
	z = tanh ( f );
	zp = fp;
}

void MLP::backward ( const DMVector& X, int c )
{
	int i, j, oz;
	double ro;
	oz = z < 0 ? -1 : 1;
	ro = zp * ( c - oz );
	for ( i = 0; i < k; ++i )
	{
		E.set ( i, W.get ( i ) * ro * Yp.get ( i ));
		W.set ( i, W.get ( i ) + eta * Y.get ( i ) * ro );
	}
	en = wn * ro;
	wn += eta * ro;

	for ( i = 0; i < k; ++i )
	{
		for ( j = 0; j < n; ++j )
		{
			V[i].set ( j, V[i].get ( j ) + eta * X.get ( j ) * E.get ( i ));
		}
		vn.set ( i, vn.get ( i ) + eta * en );
	}
}